import dnsPacket from './dns-packet'
import events from 'eventemitter3'

let MulticastDNS = function (opts) {
  if (!opts) opts = {}

  let that = new events.EventEmitter()
  let port = typeof opts.port === 'number' ? opts.port : 5353
  let type = opts.type || 'udp4'
  let ip = opts.ip || opts.host || (type === 'udp4' ? '224.0.0.251' : null)
  let me = {address: ip, port: port}
  let destroyed = false
  let netInfo = [] // networkInterface {address, socketId}

  if (type === 'udp6' && (!ip || !opts.interface)) {
    throw new Error('For IPv6 multicast you must specify `ip` and `interface`')
  }

  let noop = function () {}

  function socketInNetInfo (sockid, address) {
    for (let v of netInfo) {
      if (v.socketId === sockid || v.address === address) {
        return true
      }
    }
    return false
  }

  function socketBindAddress (address) {
    window.chrome.sockets.udp.create({}, function (createInfo) {
      let socketId = createInfo['socketId']
      if (socketId < 0) {
        that.emit('error', {msg: 'udp create error', createInfo})
      } else {
        netInfo.push({address, socketId})
        window.chrome.sockets.udp.bind(socketId, address, port, function (res) {
          if (res >= 0) {
            window.chrome.sockets.udp.joinGroup(socketId, ip, (res) => { // default ip: '224.0.0.251'
              if (res < 0) {
                that.emit('error', {msg: 'udp joinGroup error', res})
              }
            })
          } else {
            that.emit('error', {msg: 'udp sockets bind error', res})
          }
        })
      }
    })
  }

  function bindNetworkInterfaces (networkInterfaces) {
    if (!networkInterfaces.length) {
      that.emit('error', {msg: 'no network available!'})
      return
    }

    async function _bind () {
      let promises = networkInterfaces.map((networkInterface) => {
        let address = networkInterface['address']
        if (address.indexOf(':') !== -1) {
          // TODO: ipv6.
          console.warn('IPv6 address unsupported', address)
        } else {
          socketBindAddress(address)
        }
      })
      await Promise.all(promises)
    }

    _bind().then(() => that.emit('ready'))
  }

  function socketInit () {
    try {
      window.chrome.sockets.udp.onReceive.addListener(that.socketOnReceive)
      window.chrome.sockets.udp.onReceiveError.addListener(that.socketOnReceiveError)
      window.chrome.system.network.getNetworkInterfaces(bindNetworkInterfaces)
    } catch (e) {
      that.emit('error', e)
    }
  }

  function broadcast (data, rinfo, cb) {
    netInfo.forEach((netPort) => {
      window.chrome.sockets.udp.send(netPort.socketId, data, rinfo.address || rinfo.host, rinfo.port, function (sendInfo) {
        if (sendInfo.resultCode < 0) {
          that.emit('error', {msg: 'Could not send data to:' + netPort.address, socketInfo: netPort, sendInfo, rinfo})
        }
        cb(sendInfo)
      })
    })
  }

  that.send = function (value, rinfo, cb) {
    if (typeof rinfo === 'function') return that.send(value, null, rinfo)
    if (!cb) cb = noop
    if (!rinfo) rinfo = me
    if (destroyed) return cb()

    try {
      let message = dnsPacket.encode(value)
      let rawMessage = message.buffer.slice(message.byteOffset, message.byteOffset + message.byteLength) // to ArrayBuffer
      broadcast(rawMessage, rinfo, cb)
    } catch (e) {
      that.emit('error', e)
    }
  }

  that.query = function (q, type, rinfo, cb) {
    if (typeof type === 'function') return that.query(q, null, null, type)
    if (typeof type === 'object' && type && type.port) return that.query(q, null, type, rinfo)
    if (typeof rinfo === 'function') return that.query(q, type, null, rinfo)
    if (!cb) cb = noop

    if (typeof q === 'string') q = [{name: q, type: type || 'ANY'}]
    if (Array.isArray(q)) q = {type: 'query', questions: q}

    q.type = 'query'
    that.send(q, rinfo, cb)
  }

  that.response =
  that.respond = function (res, rinfo, cb) {
    if (Array.isArray(res)) res = {answers: res}
    res.type = 'response'
    that.send(res, rinfo, cb)
  }

  that.socketOnReceiveError = function (info) {
    let sock = info.socketId
    if (socketInNetInfo(sock)) {
      that.emit('error', info)
    }
  }

  that.socketOnReceive = function (info) {
    let sock = info.socketId
    if (socketInNetInfo(sock)) {
      try {
        let message = info.data
        message = dnsPacket.decode(Buffer.from(message))
        that.emit('packet', message, info)
        if (message.type === 'query') that.emit('query', message, info)
        if (message.type === 'response') that.emit('response', message, info)
      } catch (err) {
        that.emit('warning', err)
      }
    }
  }

  that.destroy = function () {
    if (!destroyed) {
      destroyed = true
      try {
        // Remove event listeners.
        window.chrome.sockets.udp.onReceive.removeListener(that.socketOnReceive)
        window.chrome.sockets.udp.onReceiveError.removeListener(that.socketOnReceiveError)
        // Close opened sockets.
        netInfo.forEach((nPort) => {
          window.chrome.sockets.udp.close(nPort.socketId)
        })
        netInfo = []
      } catch (e) {
        that.emit('error', e)
      }
    }
  }

  // init sockets
  socketInit()

  return that
}

export {
  MulticastDNS
}
